LNMP之Nginx初始配置

这一篇学习笔记主要讲述的是在LNMP刚刚搭建完后需要做的一些常用的配置和优化。

1. 默认虚拟主机

和httpd类似,在Nginx中也有默认虚拟主机,第一个被Nginx加载的虚拟主机就是默认虚拟主机。但是和httpd不同的是,它还有一个配置用来标记默认虚拟主机。也就是说,如果没有这个标记,第一个虚拟主机为默认虚拟主机。

修改配置文件nginx.conf,在结束符号}上面加入一行配置,改写如下:

1
2
include vhost/*.conf;
}

意思是,/usr/local/nginx/conf/vhost/下面的所有以.conf结尾的文件都会加载,这样我们就可以把所有虚拟主机配置文件放到vhost目录下面了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@localhost ~]# mkdir /usr/local/nginx/conf/vhost
[root@localhost ~]# cd /usr/local/nginx/conf/vhost/
[root@localhost vhost]# vim default.conf
#<==写入如下内容
server
{
listen 80 default_server; #<==有这个default_server标记的就是默认虚拟主机
server_name aaa.com;
index index.html index.htm index.php;
root /data/nginx/default;
}
#<==保存退出
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t #<==测试配置文件是否有误
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload #<==重新加载nginx的配置文件
[root@localhost vhost]# mkdir -p /data/nginx/default/
[root@localhost vhost]# echo "default_server" > /data/nginx/default/index.html
[root@localhost vhost]# ll /data/nginx/default/
总用量 4
-rw-r--r-- 1 root root 15 37 03:34 index.html
#<==上面的步骤是创建索引页
[root@localhost vhost]# curl -x127.0.0.1:80 aaa.com #<==访问aaa.com
default_server
[root@localhost vhost]# curl -x127.0.0.1:80 1212.com
default_server #<==访问一个没有定义过的域名,也会访问到aaa.com

2. 用户认证

再来创建一个新的虚拟主机:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@localhost vhost]# cd /usr/local/nginx/conf/vhost/
[root@localhost vhost]# vim test.com.conf
#<==加入如下内容
server
{
listen 80;
server_name test.com;
index index.html index.htm index.php;
root /data/nginx/test.com;
location / #<==就是配置这里
{
auth_basic "Auth";
auth_basic_user_file /usr/local/nginx/conf/htpasswd;
}
}
#<==保存退出
[root@localhost vhost]# yum install -y httpd
#<==安装httpd,用来生成密码文件
[root@localhost vhost]# htpasswd -c /usr/local/nginx/conf/htpasswd aming #<==创建aming用户
New password:
Re-type new password:
Adding password for user aming
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload

上述配置说明:

  • 核心配置语句就两行
    • auth_basic 打开认证
    • auth_basic_user_file 指定用户密码文件,当然前提是这个用户密码文件存在。
  • 生成用户密码文件的工具需要借助httpd的htpasswd,Nginx不自带这个工具。
    • 第一次生成要用-c选项,第二次就不需要用了。
  • 可以增加一句root 绝对路径来限制具体的路径。

下面使用curl工具来验证:

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost vhost]# mkdir /data/nginx/test.com
[root@localhost vhost]# echo "test.com" > /data/nginx/test.com/index.html
[root@localhost vhost]# curl -I -x127.0.0.1:80 test.com
HTTP/1.1 401 Unauthorized
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 19:49:38 GMT
Content-Type: text/html
Content-Length: 195
Connection: keep-alive
WWW-Authenticate: Basic realm="Auth"
#<==状态码为401,说明该网站需要用户验证。

如果是用浏览器打开该网站,则会有提示框,输入用户名和密码,正确输入后才可以成功访问到网站的内容。

如果是针对某个目录做用户认证,需要修改location后面的路径:

1
2
3
4
5
location /admin/
{
auth_basic "Auth"
auth_basic_user_file "/usr/local/nginx/conf/htpasswd;"
}


3. 域名重定向

Nginx的域名重定向和httpd的类似,但更容易理解,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@localhost vhost]# vim /usr/local/nginx/conf/vhost/test.com.conf
#<==添加如下内容
server
{
listen 80;
server_name test.com test1.com test2.com #<==配置多个域名
index index.html index.htm index.php;
root /data/nginx/test.com;
if ($host != 'test.com' ) #<==这段配置域名重定向
{
rewrite ^/(.*)$ http://test.com/$1 permanent;
}
}

说明:

  • 在Nginx配置中:
    • server_name 后面可以跟多个域名
    • permanent 为永久重定向,相当于httpd的R=301
    • 还有一个常用的redirect,相当于httpd的R=302

测试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost vhost]# curl -x127.0.0.1:80 test1.com/123.txt -I
HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 20:07:03 GMT
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Location: http://test.com/123.txt

关于rewrite的一个应用——伪静态rewrite规则:

1
2
3
4
5
6
rewrite ^([^\.]*)/topic~(.+)\.html$ $1/portal.php?mod=topic&topic=$2 last;
rewrite ^([^\.]*)/forum-(\w+)-([0-9+])\.html$ $1/form.php?mod=forumdisplay&fid=$2fid=$2&page=$3 last;
rewrite ^([^\.]*)/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ $1/forum.php?mod=forum.php?mod=viewthread&tid=$2&extra=page%3D$4&page=$3 last;
rewrite ^([^\.]*)/group-([0-9]+)-([0-9]+)\.html$ $1/form.php?mod=group&fid=$2&page=$3 last;
rewrite ^([^\.]*)/space-(username|uid)-(.+)\.html$ $1/home.php?mod=space&$2=$3 last;
rewrite ^([^\.]*)/(fid|tid)-([0-9]+)\.html$ $1/index.php?action=$2&value=$3 last;


4. Nginx的访问日志

4.1. 配置Nginx的日志格式

先来看看Nginx的日志格式:

1
2
3
4
[root@localhost vhost]# grep -A2 log_format /usr/local/nginx/conf/nginx.conf
log_format combined_realip '$remote_addr $http_x_forwarded_for [$time_local]'
' $host "$request_uri" $status'
' "$http_referer" "$http_user_agent"';

日志格式可以在Nginx.conf中自己定义,如下:

1
2
3
log_format main '$remote_addr - $remote_user [$time_local] $request'
'"$status" $body_bytes_sent "$http_referer"'
'"$http_user_agent" "$http_x_forwarded_for"';

下面的日志格式,会记录代理的IP和真实客户端的真实IP,建议大家平时使用这个配置:

1
2
3
log_format main1 '$proxy_add_x_forwarded_for - $remote_user [$time_local]'
'"$request" $status $body_bytes_sent'
'"$http_referer" "$http_user_agent"';

和httpd类似,也是在主配置文件中定义的日志格式。

  • combined_realip 为日志格式的名字,后面可以调用它
  • $remote_addr 为访问网站的用户的出口IP
  • $http_x_forwarded_for 为代理服务器的IP,如果使用了代理,则会记录代理的IP
  • $time_local 为当前的时间
  • $host 为访问的主机名
  • $request_uri 为访问的URL地址
  • $status 为状态码
  • $http_referer 为referer地址
  • $http_user_agent 为user_agent

然后再到虚拟主机配置文件中指定访问日志的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost vhost]# vim /usr/local/nginx/conf/vhost/test.com.conf
#<==配置内容如下:
server
{
listen 80;
server_name test.com test1.com test2.com;
index index.html index.htm index.php;
root /data/nginx/test.com;
if ($host != 'test.com' )
{
rewrite ^/(.*)$ http://test.com/$1 permanent;
}
access_log /tmp/1.log combined_realip; #<==只是这一行配置
}

使用access_log来指定日志的存储路径,最后面指定日志的格式名字,测试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost vhost]# curl -x127.0.0.1:80 test.com/111
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.10.3</center>
</body>
</html>
[root@localhost vhost]# cat /tmp/1.log
127.0.0.1 - [07/Mar/2018:04:20:07 +0800] test.com "/111" 404 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

4.2. 错误日志error_log日志级别

Nginx的error_log级别分为:debuginfonoticewarnerrorcrit。默认为crit。该日志级别在日志名后边的定义格式如下:

1
error_log /your/path/error.log crit;

crit记录的日志最少,而debug记录的日志最多。如果你的Nginx遇到一些问题,比如502比较频繁出现,但是默认的error_log并没有看到有意义的信息,那么就可以调一下错误日志的级别,当你调成error级别时,错误日志记录的内容会更加丰富。

4.3. 某些类型的文件不记录访问日志

配置如下:

1
2
3
4
location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|js|css)$
{
access_log off;
}

详细的信息参考下面的《配置静态文件不记录访问日志并添加过期时间》章节。

4.4. Nginx的日志切割

Nginx的日志很简单,不像httpd还有自带的切割工具,要想切割Nginx日志需要借助系统的切割工具或者自定义脚本。

4.4.1. 切割脚本

在这里,用一个Nginx的日志切割脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@localhost vhost]# vim /usr/local/sbin/nginx_log_rotate.sh
#<==写入如下代码
#!/bin/bash
# 假设nginx的日志存放路径为/data/logs/
d=`date -d "-1 day" +%Y%m%d`
logdir="/data/logs"
nginx_pid="/usr/local/nginx/logs/nginx.pid"
cd $logdir
for log in `ls *.log`
do
mv $log $log-$d
done
/bin/kill -HUP `cat $nginx_pid`

写完脚本后,还需要增加任务计划:

1
0 0 * * * /bin/bash /usr/local/sbin/nginx_log_rotate.sh

4.4.2. 借助系统的logrotate工具

配置过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# vim /etc/logrotate.d/nginx
#<==加入如下内容
/home/logs/*.log
{
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 644 nobody nobody
sharedscripts
postrotate
[ -f /usr/local/nginx/var/nginx.pid ] && kill -USR1 `cat /usr/local/nginx/var/nginx.pid`
endscript
}

说明:

  • 第一行就要定日日志的路径,可以是多个日志。
  • daily 表示日志按天归档。
  • missingok 表示忽略所有错误,比如日志文件不存在的情况下。
  • rotate 52 表示存放的日志个数,最多就是52个,最老的会被删除。
  • compress 表示日志要压缩。
  • delaycompress 表示压缩除了当前和最近之外的所有其他版本。
  • notifempty 表示如果日志为空,则不归档。
  • create 644 nobody nobody 定义归档日志的权限以及属主和属组。
  • sharedscripts 表示所有的日志共享该脚本,因为我们在这里指定的日志文件为多少个,用来*.log
  • postrotate 后面跟轮换过日志之后要运行的命令。
  • endscript 表示结束了。

5. 配置静态文件不记录访问日志并添加过期时间

需求和httpd中的一样,但是配置就简单很多,我们可以把指定文件类型不记录访问日志和配置静态文件过期时间的功能一起配置。虚拟主机的配置文件改成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server
{
listen 80;
server_name test.com test1.com test2.com;
index index.html index.htm index.php;
root /data/nginx/test.com;
if ($host != 'test.com' )
{
rewrite ^/(.*)$ http://test.com/$1 permanent;
}
access_log /tmp/1.log combined_realip;
location ~.*\.(gif|jpg|jpeg|png|bmp|swf)$ #<==就是配置下面这两段
{
expires 7d;
access_log off;
}
location ~.*\.(js|css)$
{
expires 12h;
access_log off;
}
}

说明:

  • 使用location ~可以指定对应的静态文件
  • expires配置过期时间
  • access_log配置为off就可以不记录访问日志了

下面来模拟测试以一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@localhost ~]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost ~]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost ~]# echo "11111111111" > /data/nginx/test.com/1.js #<==创建js文件
[root@localhost ~]# echo "22222222222" > /data/nginx/test.com/2.jpg #<==创建jpg文件
[root@localhost ~]# touch /data/nginx/test.com/1.jss #<==创建一个对比的文件
[root@localhost ~]# curl -I -x127.0.0.1:80 test.com/1.js #<==访问js类型的文件,缓存过期时间为12小时
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 21:55:27 GMT
Content-Type: application/javascript
Content-Length: 12
Last-Modified: Tue, 06 Mar 2018 21:54:20 GMT
Connection: keep-alive
ETag: "5a9f0e0c-c"
Accept-Ranges: bytes
[root@localhost ~]# curl -I -x127.0.0.1:80 test.com/2.jpg #<==访问jpg类型的文件,缓存过期时间为7小时
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 21:56:53 GMT
Content-Type: image/jpeg
Content-Length: 12
Last-Modified: Tue, 06 Mar 2018 21:54:41 GMT
Connection: keep-alive
ETag: "5a9f0e21-c"
Accept-Ranges: bytes
[root@localhost ~]# curl -I -x127.0.0.1:80 test.com/1.jss
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 21:57:29 GMT
Content-Type: application/octet-stream
Content-Length: 0
Last-Modified: Tue, 06 Mar 2018 21:55:01 GMT
Connection: keep-alive
ETag: "5a9f0e35-0"
Accept-Ranges: bytes

可以很清楚地看到Cache-cpntrol对应的时间大小。另外也可以看一下访问日志:

1
2
[root@localhost ~]# cat /tmp/1.log
127.0.0.1 - [07/Mar/2018:06:02:43 +0800] test.com "/1.jss" 200 "-" "curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.27.1 zlib/1.2.3 libidn/1.18 libssh2/1.4.2"

前面的实践中访问了js以及jpg类型的文件,都没有记录到日志里面去,可以看到我们配置的效果实现了。


6. Nginx防盗链

思路和httpd的一样,配置也不难,由于和过期时间、不记录日志有部分重合,可以把两部分组合在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server
{
listen 80;
server_name test.com test1.com test2.com;
index index.html index.htm index.php;
root /data/nginx/test.com;
if ($host != 'test.com' )
{
rewrite ^/(.*)$ http://test.com/$1 permanent;
}
access_log /tmp/1.log combined_realip;
location ~* ^.+\.(gif|jpg|jpeg|png|bmp|swf|flv|rar|zip|doc|pdf|gz|bz2|xls)$ #<==配置这些内容
{
expires 7d;
valid_referers none blocked server_names *.test.com ; #<==表示对这些域名的网站不进行防盗链
if ($invalid_referer) {
return 403;
}
access_log off;
}
}

测试过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@localhost ~]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost ~]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost ~]# curl -x127.0.0.1:80 -I -e "http://aaa.com/1.txt" test.com/2.jpg
HTTP/1.1 403 Forbidden
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 22:11:05 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
[root@localhost ~]# curl -x127.0.0.1:80 -I -e "http://test.com/1.txt" test.com/2.jpg
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Tue, 06 Mar 2018 22:11:37 GMT
Content-Type: image/jpeg
Content-Length: 12
Last-Modified: Tue, 06 Mar 2018 21:54:41 GMT
Connection: keep-alive
ETag: "5a9f0e21-c"
Expires: Tue, 13 Mar 2018 22:11:37 GMT
Cache-Control: max-age=604800
Accept-Ranges: bytes

可以看到不仅仅有过期时间,还有防盗链的功能。也就是说,这些功能可以合在一起写。


7. 控制访问

和httpd一样,Nginx也需要限制某些IP不能访问或者只允许某些IP访问。配置方法和httpd很像,但更加简洁,而且不像httpd那样全部遍历一遍。比如,我们有个需求:使访问admin目录的请求只允许192.168.1.111和127.0.0.1访问。配置文件如下:

1
2
3
4
5
6
location /admin/
{
allow 192.168.1.1;
allow 127.0.03.1;
deny all;
}

在配置httpd的时候,还有一个oeder,来先定义先allow还是先deny,在Nginx里并没有,只要匹配规则就结束了。假如来源IP为192.168.1.111,它会从上到下逐一去匹配,第一个IP(192.168.1.1)不符合,第二个IP(127.0.0.1)也不符合,直到第三行(all)的时候才匹配到,匹配的这条规则为deny(也就是拒绝访问),所以最红会返回一个403的状态码。如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost ~]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost ~]# mkdir /data/nginx/test.com/admin
[root@localhost ~]# echo "123" > /data/nginx/test.com/admin/1.html
[root@localhost ~]# curl -x127.0.0.1:80 test.com/admin/1.html
123
[root@localhost ~]# curl -x192.168.1.111:80 test.com/admin/1.html
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.10.3</center>
</body>
</html>

配置文件中的IP也可以为IP段,比如可以写成allow 192.168.1.0/24。如果只拒绝某几个IP,就可以写成这样了:

1
2
3
4
5
location /admin/
{
deny 192.168.1.1;
deny 127.0.0.1;
}

如果是黑名单的形式,就需要写allow all了,因为默认就是允许所有。除了这种简单的限制目录外,也可以根据正则匹配来限制,如下:

1
2
3
4
location ~ .*(abc|image)/.*\.php$
{
deny all;
}

小括号里面的竖线为分隔符,它们之间是或者的意思,这样就可以把访问的URL中带有abc或者image字符串,并且是PHP的请求拒绝访问。若是想把上传文件的目录禁止解析PHP,在Nginx的配置中,只需要这样几行就可以做到。

在Nginx的配置里,也可以针对user_agent做一些限制,工作中平时用得非常多。配置如下:

1
2
3
4
if ($http_user_agent ~ 'Spider/3.0|YoudaoBot|Tomato')
{
return 403;
}

其中~为匹配符号,只要user_agent中含有Spider/3.0或者YoudaoBot或者Tomato字符串的,都会被拒绝,return 403为直接返回403的状态码,当然也可以把它替换为deny all


8. Nginx解析PHP

8.1. 配置Nginx解析PHP

在LAMP中,PHP是作为httpd的一个模块出现的,只要PHP模块被加载,那么就能解析PHP脚本了。而在LNMP中,PHP是以一个服务(php-fpm)的形式存在的,首先要启动php-fpm服务,然后Nginx再和php-fpm服务通信,也就是说,处理PHP脚本解析的工作是由php-fpm来完成的,Nginx仅仅是一个搬运工,它把用户的请求传递给php-fpm,php-fpm处理完成后把结果传递给Nginx,Nginx再把结果返回给用户。那么Nginx是如何和PHP联系起来的呢?其实,在nginx.conf中已经有了展示。

下面是test.com.conf的内容,其中包含了PHP相关的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server
{
listen 80;server_name test.com test1.com test2.com;
index index.html index.htm index.php;
root /data/nginx/test.com;
if ($host != 'test.com')
{
rewrite ^/(.*)$ http://test.com/$1 permanent;
}
location ~ \.php$
{
include fastcgi_params;
fastcgi_pass unix:/tmp/php-fcgi.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /data/nginx/tset.com$fastcgi_script_name;
}
access_log /tmp/1.log combined_realip;
}

说明:

  • 其中,fastcgi_pass用来指定php-fpm的地址,如果php-fpm监听的是一个tcp:port的地址(比如127.0.0.1:9000),那么也需要在这里改成fastcgi_pass 127.0.0.1:9000。这个地址一定要和php-fpm服务监听的地址匹配,否则会报502错误。
  • 还有一个地方需要注意,fastcgi_param SCRIPT_FILENAME后面跟的路径为该站点的根目录,和前面定义的root那个路径保持一致。如果这里配置不对,访问PHP页面会出现404。

8.2. 针对目录限制PHP解析

示例配置如下:

1
2
3
4
location ~ .*(diy|template|attachments|forumdata|attachment|image)/.*\.php$
{
deny all;
}


9. Nginx禁止指定user_agent

和httpd的相关配置也挺像,具体如下:

1
2
3
4
5
6
lication /
{
if ($http_user_agent ~ 'bingbot/2.0|MJ12bot/v1.4.2|Spider/3.0|YoudaoBot|Gecko/20100315'){
return 403;
}
}


10. Nginx代理

10.1. Nginx代理

Nginx的代理功能非常实用,这也是Nginx比httpd越来越受欢迎的一个原因。一家公司有很多台服务器,为了节省成本,不能为所有服务器都分配公网IP,而如果一个没有公网IP的服务要提供Web服务,就可以通过代理来实现。

如果Nginx后面有多台Web服务器,如果同时代理,那Nginx在这里还可以用upstream起到一个负载均衡的作用,这个功能在生产环境中用得也特别多。

先来看一下Nginx的代理如何配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@localhost ~]# cd /usr/local/nginx/conf/vhost/
[root@localhost vhost]# vim proxy.com #<==写如如下内容
server
{
listen 80;
server_name ask.apelearn.com;
location /
{
proxy_pass http://121.201.9.155/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

上述配置内容的说明:

  • 前面两行不用解释,和普通的虚拟主机一样,不同的是后面proxy相关的语句。
  • proxy_pass指定要代理的域名所在的服务器IP,这里的IP是阿铭论坛所在服务器的IP,可以通过ping ask.apelearn.com获取到该IP。
  • 后面的三行为定义发往后端Web服务器的请求头,第二行必须有,否则代理不成功,它表示后端Web服务器的域名和当前配置文件中的server_name保持一致。第三行和第四行可以省略,在前面讲述Nginx日志格式的时候介绍过这两个参数,表示的含义是一样的。

配置文件保存后,重新加载Nginx服务并验证:‘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost vhost]# curl -x127.0.0.1:80 ask.apelearn.com -I
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Wed, 07 Mar 2018 07:33:28 GMT
Content-Type: text/html
Content-Length: 15
Last-Modified: Tue, 06 Mar 2018 19:34:12 GMT
Connection: keep-alive
ETag: "5a9eed34-f"
Accept-Ranges: bytes
[root@localhost vhost]# curl ask.apelearn.com -I
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Sun, 11 Mar 2018 00:51:48 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
X-Powered-By: PHP/5.3.29
P3P: CP="CURa ADMa DEVa PSAo PSDo OUR BUS UNI PUR INT DEM STA PRE COM NAV OTC NOI DSP COR"
Set-Cookie: ape__Session=v02nfaoem0s7edfddr4is9lud7; path=/; domain=.apelearn.com
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache

这样看是没有问题的,虚拟机的Nginx版本为1.10.3,而阿铭论坛服务器上用的是1.8.0。其实,把服务器上的环境版本暴漏出来并不安全,因为这样别有用心的人会直接获取到服务器上的软件版本信息,如果该版本正好有漏洞,那么他就可以直接攻击了。关于如何关掉版本信息,不再这里记录了。可以查取相关资料。

10.2. Nginx代理所有域名

我们还可以代理一个服务器上的所有域名。首先在vhost目录下需要创建两个文件,一个是servername列表文件,另一个是虚拟主机配置文件。两个文件内容分别如下:

  • servername

    1
    2
    3
    4
    [root@localhost ~]# vim /usr/local/nginx/conf/vhost/servername
    #<==加入如下内容
    server_name www.123.net.cn www.alsdjfl.com www.asdfal.com;
    • 就是这么简单一行,当然这个server_name后面还可以继续添加。
  • 虚拟主机配置文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    [root@localhost ~]# vim /usr/local/nginx/conf/vhost/proxy_all.conf
    #<==加入如下内容
    server
    {
    listen 80;
    include vhosts/servername; #<==这里的文件就是上边那个servername列表文件
    location /
    {
    proxy_pass http://1.2.1.2/; #<==这里就是需要做代理的服务器IP了
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    access_log /dev/null;
    }

10.3. Nginx配置负载均衡

下面再提供一个负载均衡的示例,在编写Nginx虚拟主机配置文件之前,先来查看一下www.qq.com域名对应的IP,使用dig命令(若是没有这个命令,安装它yum install -y bind-utils):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost vhost]# dig www.qq.com
; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.62.rc1.el6_9.5 <<>> www.qq.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30222
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;www.qq.com. IN A
;; ANSWER SECTION:
www.qq.com. 299 IN A 125.39.240.113
www.qq.com. 299 IN A 61.135.157.156
;; Query time: 95 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Wed Mar 7 15:41:58 2018
;; MSG SIZE rcvd: 60

可以看到有两个IP,这两个IP都可以访问到www.qq.com。先来验证一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@localhost vhost]# curl -x61.135.157.156:80 www.qq.com -I
HTTP/1.1 200 OK
Server: squid/3.5.24
Date: Sun, 11 Mar 2018 01:01:43 GMT
Content-Type: text/html; charset=GB2312
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
Expires: Sun, 11 Mar 2018 01:02:43 GMT
Cache-Control: max-age=60
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Cache: HIT from tianjin.qq.com
[root@localhost vhost]# curl -x125.39.240.113:80 www.qq.com -I
HTTP/1.1 200 OK
Server: squid/3.5.24
Date: Sun, 11 Mar 2018 01:01:43 GMT
Content-Type: text/html; charset=GB2312
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
Expires: Sun, 11 Mar 2018 01:02:43 GMT
Cache-Control: max-age=60
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Cache: MISS from tianjin.qq.com

可以看到两个IP返回的结果是一样的,它使用的Web Server软件为squid(一种代理软件)。有两个IP就可以走负载均衡了,配置过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@localhost vhost]# vim /usr/local/nginx/conf/vhost/load.conf #<==写入如下内容
upstream qq_com
{
ip_hash;
server 61.135.157.156:80;
server 125.39.240.113:80;
}
server
{
listen 80;
server_name www.qq.com;
location /
{
proxy_pass http://qq_com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

和简单的代理有所不同,负载均衡多了一个upstream,在这里定义后端的Web Server,可以是一个,也可以是多个。其中ip_hash为负载均衡的算法,它表示根据IP地址把请求分到不通的服务器上。比如用户A的IP为1.1.1.1,用户B的IP为2.2.2.2,则A访问的时候会把请求转发到第一个Web Server上,而B访问的时候会到第二个Web Server上。这种算法用在把session存到本机磁盘上的情况,至于什么是session,其实就是访问信息,举个例子就像你登陆一个网站,服务器上就会记录你的session信息,这个session返回保存一段时间,等你休息一段时间后再看该网站,你会发现你还是登陆的状态,这就是因为session还存在这台服务器上。

下面是测试结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost vhost]# /usr/local/nginx/sbin/nginx -s reload
[root@localhost vhost]# curl -x127.0.0.1:80 www.qq.com -I
HTTP/1.1 200 OK
Server: nginx/1.10.3
Date: Wed, 07 Mar 2018 07:59:10 GMT
Content-Type: text/html; charset=GB2312
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
Expires: Sun, 11 Mar 2018 01:18:03 GMT
Cache-Control: max-age=60
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Cache: HIT from tianjin.qq.com

这样,虚拟机通过这种配置就可以访问www.qq.com了,这其实就是代理的作用。


11. Nginx配置SSL

HTTPS是一种加密的HTTP协议。如果HTTP通信的数据包在传输过程中被截取,我们可以破译这些数据包里面的信息,这里不乏一些用户名、密码、手机号等敏感信息。而如果使用HTTPS通信,即使数据包被截获,我们也无法破译里面的内容。

HTTPS的通信过程大致如下:

  1. 浏览器发送一个HTTPS请求给服务器
  2. 服务器要有一套数字整数,可以自己制作(后面的演示就是用的自己制作的整数),也可以向组织申请,区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面,这套证书其实就是一对公钥和私钥。
  3. 服务器会把公钥传输给客户端
  4. 客户端(即浏览器)收到公钥后,会验证其是否合法有效,无效会有警告提醒,有效则会生成一串随机字符串,并用收到的公钥加密
  5. 客户端把加密后的随机字符串传输给服务器
  6. 服务器收到加密后的随机字符串后,先用私钥解密(公钥加密,私钥解密),获取到这一串随机字符串后,再用这串随机字符串加密传输的数据(该加密为”对称加密;所谓对称加密,就是将数据和私钥,也就是这个随机字符串通过某种算法混合在一起,这样除非知道私钥,否则无法获取数据内容”)
  7. 服务器把加密后的数据传输给客户端
  8. 客户端收到数据后,再用自己的私钥(也就是哪个随机字符串)解密。

通过上文的简要分析,我们可以确定服务器上必须有一对公钥和私钥,也就是后面提到的SSL证书。如果是公司的网站对外提供服务,则需要购买被各大浏览器厂商认可的SSL证书。目前各大SSL整数服务商已经不再提供免费的SSL整数服务,所以做本实验的时候,只能在Linux机器上生成一对自定义的SSL证书,这个证书只能我们自己玩一玩,不能使用在生产环境中。具体配置过程如下:

  • 第一步

    1
    2
    3
    4
    5
    6
    7
    8
    [root@localhost vhost]# cd /usr/local/nginx/conf/
    [root@localhost conf]# openssl genrsa -des3 -out tmp.key 2048
    Generating RSA private key, 2048 bit long modulus
    ....................+++
    ........................................................................................................+++
    e is 65537 (0x10001)
    Enter pass phrase for tmp.key:
    Verifying - Enter pass phrase for tmp.key:
    • openssl命令如果没有安装,使用这个命令安装yum install -y openssl
    • 这一步是生成key文件(通常成为私钥)
    • 2048为加密字符串长度
    • 会提示让我们输入一个密码,密码不能太短,否则不成功
  • 第二步

    1
    2
    3
    [root@localhost conf]# openssl rsa -in tmp.key -out aminglinux.key
    Enter pass phrase for tmp.key:
    writing RSA key
    • 这一步是把刚刚生成的tmp.key再转换成aminglinux.key,目的是删除刚才设置的密码
    • 如果key文件有密码,则必须在Nginx加载它的时候输入它的密码,因此很不方便
  • 第三步

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [root@localhost conf]# rm -f tmp.key
    [root@localhost conf]# openssl req -new -key aminglinux.key -out aminglinux.csr
    You are about to be asked to enter information that will be incorporated
    into your certificate request.
    What you are about to enter is what is called a Distinguished Name or a DN.
    There are quite a few fields but you can leave some blank
    For some fields there will be a default value,
    If you enter '.', the field will be left blank.
    -----
    Country Name (2 letter code) [XX]:
    State or Province Name (full name) []:
    Locality Name (eg, city) [Default City]:
    Organization Name (eg, company) [Default Company Ltd]:
    Organizational Unit Name (eg, section) []:
    Common Name (eg, your name or your server's hostname) []:aming.com
    Email Address []:
    Please enter the following 'extra' attributes
    to be sent with your certificate request
    A challenge password []:123456
    An optional company name []:
    • 这一步是生成证书请求文件,这个并不是上文提到的公钥,但这个文件是必须要有的,我们要拿key文件和这个CSR文件一起生成最终的公钥文件。
    • 其中Common Name为后面配置Nginx配置文件的server_name
  • 第四步

    1
    2
    3
    4
    [root@localhost conf]# openssl x509 -req -days 365 -in aminglinux.csr -signkey aminglinux.key -out aminglinux.crt
    Signature ok
    subject=/C=XX/L=Default City/O=Default Company Ltd/CN=aming.com
    Getting Private key
    • 这样最终才生成了CRT证书文件,也就是公钥。

以上操作的最终目的就是生成aminglinux.keyaminglinux.crt两个为念。其实购买的SSL证书主要是得到这两个文件,有了这两个文件就可以配置Nginx了。配置过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@localhost conf]# vim /usr/local/nginx/conf/vhost/ssl.conf
#<==写入如下内容
server
{
listen 443;
server_name aming.com;
index index.html index.php;
root /data/nginx/aming.com;
ssl on;
ssl_certificate aminglinux.crt;
ssl_certificate_key aminglinux.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
location ~ \.php$
{
include fastcgi_params;
fastcgi_pass unix:/tmp/php-fcgi.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /data/nginx/aming.com$fastcgi_script_name;
}
access_log /tmp/1.log combined_realip;
}

保存配置文件后,检查配置是否有问题:

1
2
3
[root@localhost conf]# /usr/local/nginx/sbin/nginx -t
nginx: [emerg] unknown directive "ssl" in /usr/local/nginx/conf/vhost/ssl.conf:8
nginx: configuration file /usr/local/nginx/conf/nginx.conf test failed

果然配置有问题。通过错误提示,说明当前的Nginx并不支持SSL,这是因为在先前的Nginx编译时,并没有额外配置支持SSL的参数,要解决该问题只能重新百衲衣以便Nginx。操作过程如下:

1
2
3
4
[root@localhost conf]# cd /usr/local/src/nginx-1.10.3
[root@localhost nginx-1.10.3]# ./configure --prefix=/usr/local/nginx --with-http_ssl_module
[root@localhost conf]# make
[root@localhost conf]# make install

编译重新安装完成后,再来检验一次:

1
2
3
4
[root@localhost nginx-1.10.3]# /usr/local/nginx/sbin/nginx -t
nginx: the configuration file /usr/local/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/local/nginx/conf/nginx.conf test is successful
[root@localhost nginx-1.10.3]# /usr/local/nginx/sbin/nginx -s reload

发现没有问题后,创建对应的目录和测试文件:

1
2
3
[root@localhost nginx-1.10.3]# mkdir /data/nginx/aming.com
[root@localhost nginx-1.10.3]# echo "<?php phpinfo(); ?>" > /data/nginx/aming.com/1.php
[root@localhost nginx-1.10.3]# /etc/init.d/nginx restart

再编辑真实机上的hosts文件,写入一行:

1
192.168.1.111 aming.com

之后用浏览器访问https//aming.com/1.php即可看到效果。

0%